package org.brownflat.watergate.scm.svn;

import org.apache.log4j.Logger;
import org.brownflat.watergate.config.ConfigurationProperties;
import org.brownflat.watergate.domain.Branch;
import org.brownflat.watergate.domain.Commit;
import org.brownflat.watergate.domain.CommitElement;
import org.brownflat.watergate.domain.CommitElementAction;
import org.brownflat.watergate.scm.DiffOutput;
import org.brownflat.watergate.scm.EmptyDiffOutput;
import org.brownflat.watergate.scm.RepositoryClient;
import org.brownflat.watergate.scm.RepositoryClientException;
import org.brownflat.watergate.scm.Revision;
import org.brownflat.watergate.scm.Revisions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNLogEntry;
import org.tmatesoft.svn.core.SVNProperties;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNDiffClient;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

/**
 * <p>&copy Rupert Jones 2011</p>
 *
 * @author rup
 */
@Service("subversionClient")
@Scope("singleton")
public class SubversionClient implements RepositoryClient {

    private static final Logger LOG = Logger.getLogger(SubversionClient.class);

    private SVNClientManager clientManager = null;

    @Autowired
    private SubversionRevisionBuilder subversionRevisionBuilder;

    @Autowired
    private ConfigurationProperties configurationProperties;

    private SVNDiffClient diffClientManager;

    public SubversionClient() {
        DAVRepositoryFactory.setup();
        clientManager = SVNClientManager.newInstance();
        diffClientManager = clientManager.getDiffClient();
    }

    public boolean checkConnection(String path, String username, String password) {
        SVNRepository repository = null;
        try {
            ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(username, password);
            repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(path));
            repository.setAuthenticationManager(authManager);
            repository.log(new String[] {""}, null, 0, -1, true, true);
        } catch (SVNException e) {
            LOG.error("IncursiveCheck connection failed", e);
            return false;
        }
        return true;
    }

    @Override
    public Revisions getCompleteHistory(Branch branch, long from) {
        return getCompleteHistory(new String[] {""}, branch, from);
    }

    @Override
    public Revisions getCompleteHistory(String[] paths, Branch branch, long from) {
        List<Revision> subversionRevisions = new ArrayList<Revision>();
        try {
            SVNRepository repository = getRepository(branch);
            Collection logEntries = repository.log(paths, null, from, -1, true, true);

            for (Object entry : logEntries) {
                subversionRevisions.add(buildRevision((SVNLogEntry) entry));
            }
            repository.closeSession();
            return new SubversionRevisions(subversionRevisions);
        } catch (SVNException e) {
            throw new RepositoryClientException(e);
        }
    }

    @Override
    public Revisions getCompleteHistory(Branch branch) {
        return getCompleteHistory(branch, 0);
    }

    @Override
    public Revision getServerCopyRevision(Branch branch) {
        List<Revision> subversionRevisions = getCompleteHistory(branch).getRevisions();
        LinkedList<Revision> completeHistory = new LinkedList<Revision>(subversionRevisions);
        return completeHistory.peekLast();
    }

    @Override
    public Revision getFirstRevision(Branch branch) {
        Revisions revisions = getCompleteHistory(branch);
        return revisions.getFirstRevision();
    }

    @Override
    public Revisions getRemainingRevisions(Branch branch, long from) {
        Revisions completeHistory = getCompleteHistory(branch);
        List<Revision> remainingHistory = new ArrayList<Revision>();

        for (Revision subversionRevision : completeHistory) {
            if (subversionRevision.getRevision() <= from) {
                continue;
            }
            remainingHistory.add(subversionRevision);
        }
        return new SubversionRevisions(remainingHistory);
    }

    @Override
    public Revision getDetailedRevision(Branch branch, long revisionNumber) {
        try {
            SVNRepository repository = getRepository(branch);
            Collection logEntries = repository.log(new String[] {""}, null, revisionNumber, revisionNumber, true, true);

            if (logEntries.size() > 0) {
                return buildRevision((SVNLogEntry) logEntries.iterator().next());
            }
        } catch (SVNException svne) {
            throw new RepositoryClientException(svne);
        }
        return null;
    }

    @Override
    public DiffOutput diff(CommitElement commitElement) {
        SubversionDiffOutput diffOutput = new SubversionDiffOutput();
        try{
            Commit commit = commitElement.getCommit();
            Branch branch = commit.getBranch();
            Revisions revisions = getCompleteHistory(new String[] {commitElement.getPath()}, branch, 0);

            CommitElementAction commitElementAction = commitElement.getCommitElementAction();
            if(commitElementAction.equals(CommitElementAction.ADD) || commitElementAction.equals(CommitElementAction.DELETE)) {
                return new EmptyDiffOutput();
            }

            long afterChangeRevision = commitElement.getCommit().getRevision();
            Revision beforeChangeRevisionObject = revisions.getRevisionBefore(afterChangeRevision);
            if(beforeChangeRevisionObject == null) {
                return new EmptyDiffOutput();
            }
            long beforeChangeRevision = beforeChangeRevisionObject.getRevision();

            SubversionFileOutput beforeFile = new SubversionFileOutput();
            SVNRepository svnRepository = getRepository(commit.getBranch());
            svnRepository.getFile(commitElement.getPath(), beforeChangeRevision, new SVNProperties(), beforeFile);

            SubversionFileOutput afterFile = new SubversionFileOutput();
            svnRepository.getFile(commitElement.getPath(), afterChangeRevision, new SVNProperties(), afterFile);

            SVNDiffClient diffClient = new SVNDiffClient(getAuthenticationManager(branch), null);
            SVNURL url = SVNURL.parseURIDecoded(commitElement.getFullPath());

            diffClient.doDiff(url, SVNRevision.create(beforeChangeRevision),
                    SVNRevision.create(beforeChangeRevision), SVNRevision.create(afterChangeRevision), SVNDepth.INFINITY, true, diffOutput);

            diffOutput.setBeforeFile(beforeFile);
            diffOutput.setAfterFile(afterFile);

        } catch (SVNException svne) {
            throw new RepositoryClientException(svne);
        }
        return diffOutput;
    }

    @SuppressWarnings("unchecked")
    private Revision buildRevision(SVNLogEntry entry) {
        return subversionRevisionBuilder.build(entry);
    }

    private ISVNAuthenticationManager getAuthenticationManager(Branch branch) {
        ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(branch.getRepository().getUsername(), branch.getRepository().getPassword());
        return authManager;
    }

    private SVNRepository getRootRepository(Branch branch) {
        try {
            SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(branch.getRepository().getUrl()));
            repository.setAuthenticationManager(getAuthenticationManager(branch));
            return repository;
        } catch (SVNException svne) {
            throw new RepositoryClientException(svne);
        }
    }

    private SVNRepository getRepository(Branch branch) {
        try {
            SVNRepository repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(branch.getFullPath()));
            repository.setAuthenticationManager(getAuthenticationManager(branch));
            return repository;
        } catch (SVNException svne) {
            throw new RepositoryClientException(svne);
        }
    }
}
